// rememberMeCookie-with-token.js
export class RememberMeCookie {
  // 如果不传 token，这里会尝试从 sessionStorage/localStorage 里读取并解密 'sessionKey'
  constructor(sessionKey = null) {
    this.sessionKey = null; // 先初始化为 null
    this.CRED_COOKIE = 'rememberMe';
    this.SALT_COOKIE = 'rememberMeSalt';
    this._initPromise = this.initSessionKey(sessionKey);
  }

  // 异步初始化 sessionKey
  async initSessionKey(sessionKey) {
    if (sessionKey) {
      this.sessionKey = sessionKey;
    } else {
      this.sessionKey = await RememberMeCookie.getSessionKey();
    }
  }

  // 确保初始化完成
  async ensureInitialized() {
    await this._initPromise;
    if (!this.sessionKey) {
      throw new Error('Missing sessionKey: 请先用 RememberMeCookie.saveSessionKey(token) 保存 token，或在构造时传入 token');
    }
  }

  /** ---------- sessionKey 管理（静态方法，方便在登录后调用） ---------- */

  // token 加密密钥（可以根据需要修改）
  static TOKEN_SECRET = 'your-secret-key-for-token-encryption';

  // 加密 token
  static async encryptToken(token) {
    const enc = new TextEncoder();
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      enc.encode(this.TOKEN_SECRET),
      { name: 'PBKDF2' },
      false,
      ['deriveBits', 'deriveKey']
    );

    const key = await crypto.subtle.deriveKey(
      {
        name: 'PBKDF2',
        salt: enc.encode('token-salt'),
        iterations: 100000,
        hash: 'SHA-256'
      },
      keyMaterial,
      { name: 'AES-GCM', length: 256 },
      false,
      ['encrypt']
    );

    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encrypted = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv },
      key,
      enc.encode(token)
    );

    // 将 IV 和加密数据拼接后转为 base64
    const combined = new Uint8Array(iv.length + encrypted.byteLength);
    combined.set(iv);
    combined.set(new Uint8Array(encrypted), iv.length);
    return btoa(String.fromCharCode(...combined));
  }

  // 解密 token
  static async decryptToken(encryptedToken) {
    try {
      const enc = new TextEncoder();
      const keyMaterial = await crypto.subtle.importKey(
        'raw',
        enc.encode(this.TOKEN_SECRET),
        { name: 'PBKDF2' },
        false,
        ['deriveBits', 'deriveKey']
      );

      const key = await crypto.subtle.deriveKey(
        {
          name: 'PBKDF2',
          salt: enc.encode('token-salt'),
          iterations: 100000,
          hash: 'SHA-256'
        },
        keyMaterial,
        { name: 'AES-GCM', length: 256 },
        false,
        ['decrypt']
      );

      const combined = Uint8Array.from(atob(encryptedToken), c => c.charCodeAt(0));
      const iv = combined.slice(0, 12);
      const ciphertext = combined.slice(12);

      const decrypted = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv },
        key,
        ciphertext
      );

      return new TextDecoder().decode(decrypted);
    } catch (e) {
      console.error('Token 解密失败：', e);
      return null;
    }
  }

  // 保存服务端下发的 token 到 sessionStorage 或 localStorage（persist=true 则写 localStorage）
  static async saveSessionKey(token, persist = false) {
    if (!token) return;
    const encryptedToken = await this.encryptToken(token);
    if (persist) {
      localStorage.setItem('sessionKey', encryptedToken);
    } else {
      sessionStorage.setItem('sessionKey', encryptedToken);
      // 移除 localStorage 里可能旧的 sessionKey，避免冲突
      localStorage.removeItem('sessionKey');
    }
  }

  // 获取并解密 sessionKey
  static async getSessionKey() {
    const encryptedToken = sessionStorage.getItem('sessionKey') || localStorage.getItem('sessionKey');
    if (!encryptedToken) return null;
    return await this.decryptToken(encryptedToken);
  }

  // 清除 sessionKey（同时清 sessionStorage 和 localStorage）
  static clearSessionKey() {
    sessionStorage.removeItem('sessionKey');
    localStorage.removeItem('sessionKey');
  }

  /** ---------- 盐（salt）管理 ---------- */

  // 生成随机盐（16 字节），返回 base64 字符串
  static generateSalt() {
    const salt = crypto.getRandomValues(new Uint8Array(16));
    return btoa(String.fromCharCode(...salt));
  }

  setSaltCookie(saltBase64, days = 30) {
    const d = new Date();
    d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
    document.cookie = `${this.SALT_COOKIE}=${saltBase64};expires=${d.toUTCString()};path=/;Secure;SameSite=Strict`;
  }

  getSaltCookie() {
    const cookie = document.cookie.split('; ').find(row => row.startsWith(this.SALT_COOKIE + '='));
    return cookie ? cookie.split('=')[1] : null;
  }

  clearSaltCookie() {
    document.cookie = `${this.SALT_COOKIE}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
  }

  /** ---------- 密钥派生与加解密（内部方法） ---------- */

  // 从 sessionKey + salt 派生 AES-GCM 密钥
  async deriveKey(saltBase64) {
    await this.ensureInitialized();
    const enc = new TextEncoder();
    const pwKey = await crypto.subtle.importKey(
      'raw',
      enc.encode(this.sessionKey),
      'PBKDF2',
      false,
      ['deriveKey']
    );
    return crypto.subtle.deriveKey(
      {
        name: 'PBKDF2',
        salt: Uint8Array.from(atob(saltBase64), c => c.charCodeAt(0)),
        iterations: 100000,
        hash: 'SHA-256'
      },
      pwKey,
      { name: 'AES-GCM', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );
  }

  async encrypt(plainText, saltBase64) {
    const key = await this.deriveKey(saltBase64);
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const enc = new TextEncoder();
    const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, enc.encode(plainText));
    const ivBase64 = btoa(String.fromCharCode(...iv));
    const ctBase64 = btoa(String.fromCharCode(...new Uint8Array(ct)));
    return `${ivBase64}.${ctBase64}`;
  }

  async decrypt(encryptedData, saltBase64) {
    const [ivBase64, ctBase64] = (encryptedData || '').split('.');
    if (!ivBase64 || !ctBase64) throw new Error('Invalid encrypted data');
    const iv = Uint8Array.from(atob(ivBase64), c => c.charCodeAt(0));
    const ct = Uint8Array.from(atob(ctBase64), c => c.charCodeAt(0));
    const key = await this.deriveKey(saltBase64);
    const plainBuffer = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);
    return new TextDecoder().decode(plainBuffer);
  }

  /** ---------- 对外 API：写、读、删账号密码 ---------- */

  // 将 {username, password} 加密后写 cookie
  // days: cookie 有效天数
  async setCredentials(username, password, days = 30) {
    if (!username || !password) throw new Error('username 和 password 不能为空');
    // 确保有 sessionKey
    await this.ensureInitialized();

    // 取得/生成 salt
    let salt = this.getSaltCookie();
    if (!salt) {
      salt = RememberMeCookie.generateSalt();
      this.setSaltCookie(salt, days);
    }

    const payload = JSON.stringify({ username, password });
    const encrypted = await this.encrypt(payload, salt);
    const d = new Date();
    d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
    document.cookie = `${this.CRED_COOKIE}=${encrypted};expires=${d.toUTCString()};path=/;Secure;SameSite=Strict`;
  }

  // 读取并解密，返回 {username, password} 或 null
  async getCredentials() {
    await this.ensureInitialized();
    const cookie = document.cookie.split('; ').find(row => row.startsWith(this.CRED_COOKIE + '='));
    if (!cookie) return null;
    const value = cookie.split('=')[1];
    const salt = this.getSaltCookie();
    if (!salt) return null;
    try {
      const plain = await this.decrypt(value, salt);
      return JSON.parse(plain);
    } catch (e) {
      // 解密失败（可能 sessionKey 不对或数据被篡改/损坏）
      console.warn('RememberMe 解密失败：', e);
      return null;
    }
  }

  // 删除凭据 cookie 与 salt
  deleteCredentials() {
    document.cookie = `${this.CRED_COOKIE}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
    this.clearSaltCookie();
  }
}
